﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;

namespace Nirvana
{
    public sealed class NetClient
    {
        public delegate void DisconnectDelegate();
        public delegate void ConnectDelegate(bool isCompleted);
        public delegate void SendDelegate();
        public delegate void ReceiveDelegate(byte[] message, uint length);

        private static Logger logger = LogSystem.GetLogger("NetClient");

        private const int headsize = 4096;
        public Socket socket;
        //是否处于数据发送状态
        private bool isSending = false;
        private NetReceiveBody mNetReceiveBody = new NetReceiveBody(headsize);//接受包体,4k
        private byte[] tmpReceiveData = new byte[1024*16];//16kb,临时接收包体数据数组
        private Queue<NetSendBody> queue = new Queue<NetSendBody>();

        private DisconnectDelegate disconnetaction;

        public event DisconnectDelegate DisconnectEvent
        {
            add
            {
                this.disconnetaction += value;
            }
            remove
            {
                this.disconnetaction -= value;
            }
        }

        private ReceiveDelegate receiveaction;
        public event ReceiveDelegate ReceiveEvent
        {
            add
            {
                this.receiveaction +=  value;
            }

            remove
            {
                this.receiveaction -=  value;
            }
        }

        public int ReceiveTimeout
        {
            get
            {
                return this.socket.ReceiveTimeout;
            }
            set
            {
                this.socket.ReceiveTimeout = value;
            }
        }

        public int SendTimeout
        {
            get
            {
                return this.socket.SendTimeout;
            }
            set
            {
                this.socket.SendTimeout = value;
            }
        }

        /// <summary>
        /// 开启TCP keepalive探测：socket在指定时间内没有数据发送，则自动开启keepalive
        /// 有响应则结束，无响应则发送次数后，自动断开socket,响应异常
        /// </summary>
        /// <param name="onOff">1：开启，0：关闭</param>
        /// <param name="keepAliveTime">毫秒，多少时间内数据没交互就启动</param>
        /// <param name="keepAliveInterval">启动后，每隔多少时间发送一次</param>
        public void SetKeepAlive(uint onOff, uint keepAliveTime, uint keepAliveInterval)
        {
            byte[] array = new byte[12];
            BitConverter.GetBytes(onOff).CopyTo(array, 0);
            BitConverter.GetBytes(keepAliveTime).CopyTo(array, 4);
            BitConverter.GetBytes(keepAliveInterval).CopyTo(array, 8);
            this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, array);
        }

        /// <summary>
        /// 连接socket
        /// </summary>
        /// <param name="host"></param>
        /// <param name="port"></param>
        /// <param name="connectDelegate">连接状态回调</param>
        public void Connect(string host,int port, ConnectDelegate connectDelegate = null)
        {
            //已创建完成过
            if (this.socket != null)
            {
                connectDelegate?.Invoke(false);
                return;
            }
            var c = new NetClientConnect();
            c.connectDelegate = connectDelegate;
            c.port = port;
            c.netclient = this;
            //获取本机IP地址是：ipv4,ipv6
            Dns.BeginGetHostAddresses(host, new AsyncCallback(c.Connect), null);
        }

        /// <summary>
        /// socket断开
        /// </summary>
        public void Disconnect()
        {
            try
            {
                if (this.socket.Connected)
                {
                    //断开时，把所有未发送完的数据全部一起发送
                    if(this.queue.Count > 0)
                    {
                        List<ArraySegment<byte>> list = new List<ArraySegment<byte>>();
                        foreach(var u in this.queue)
                        {
                            var item = new ArraySegment<byte>(u.GetBytes(), 0, u.GetLength());
                            list.Add(item);
                        }
                        this.queue.Clear();
                        this.socket.Send(list, SocketFlags.None);
                    }

                    this.socket.Shutdown(SocketShutdown.Both);
                    this.socket.Close();
                    this.disconnetaction?.Invoke();
                }
            }
            catch(Exception ex)
            {
                logger.LogWarning("Socket disconnect: " + ex.Message);
            }
        }

        public void Close()
        {
            this.socket.Close();
            this.disconnetaction?.Invoke();
        }

        #region Send Msg
        /// <summary>
        /// 发送字节数据
        /// </summary>
        /// <param name="data"></param>
        /// <param name="sendDelegate"></param>
        public void SendMsg(byte[] data,SendDelegate sendDelegate = null)
        {
            if (!this.socket.Connected)
            {
                this.socket.Close();
                this.disconnetaction?.Invoke();
                return;
            }
            var u = NetSendBody.PopSerial();
            u.WriteStream(data);
            u.Offset = 0;
            u.CallBack = sendDelegate;

            this.queue.Enqueue(u);

            if (!this.isSending)
            {
                StartSend();
            }
        }


        public void StartSend()
        {
            if(this.queue.Count > 0)
            {
                var u = this.queue.Dequeue();
                try
                {
                    this.isSending = true;
                    this.socket.BeginSend(u.GetBytes(), u.Offset, u.GetLength() - u.Offset, SocketFlags.None, new AsyncCallback(this.OnSendSuccess), u);
                }
                catch(Exception ex)
                {
                    logger.LogWarning("Socket send error: " + ex.Message);
                    this.isSending = false;
                    this.socket.Close();
                    this.disconnetaction?.Invoke();
                }
            }
            else
            {
                this.isSending = false;
            }
        }

        private void OnSendSuccess(IAsyncResult asyncResult)
        {
            try
            {
                var u = (NetSendBody)asyncResult.AsyncState;
                int sendLength = this.socket.EndSend(asyncResult);

                u.Offset = u.Offset + sendLength;//记录发送了多少字节，以便断包时，继续发送剩下的包数据
                if(u.Offset < u.GetLength())
                {
                    this.socket.BeginSend(u.GetBytes(), u.Offset, u.GetLength() - u.Offset, SocketFlags.None, new AsyncCallback(this.OnSendSuccess), u);
                }
                else
                {
                    var t = new NetClientSend();
                    t.netclient = this;
                    t.netSendBody = u;
                    t.sendDelegate = (SendDelegate)u.CallBack;

                    Scheduler.PostTask(new Action(t.OnSendSuccess));
                }
            }
            catch (ObjectDisposedException ex)
            {
                Debug.LogWarning("Socket send 0 : " + ex.Message);
            }
            catch (Exception ex)
            {
                logger.LogWarning("Socket send 1 : " + ex.Message);
                Scheduler.PostTask(new Action(this.Close));
            }
        }

        #endregion

        #region Receive Msg

        /// <summary>
        /// 开始等待接受网络数据
        /// </summary>
        public void StartReceive()
        {
            try
            {
                //设置最大一次接收2kb，多余的存放socket缓冲中下次再次接收取出
                int offset = this.mNetReceiveBody.Copy(2048);
                this.socket.BeginReceive(this.mNetReceiveBody.GetBody(), offset, 2048, SocketFlags.None, new AsyncCallback(this.OnReceiveSuccess), this);
            }
            catch(Exception ex)
            {
                logger.LogWarning("Socket receive: " + ex.Message);
                this.socket.Close();
                this.disconnetaction?.Invoke();
            }
        }

        private void OnReceiveSuccess(IAsyncResult asyncResult)
        {
            try
            {
                int receivelength = this.socket.EndReceive(asyncResult);

                var u = new NetClientReceive();
                u.netclient = this;
                u.receivelength = receivelength;

                if (receivelength > 0)//有接收到字节数据
                {
                    Scheduler.PostTask(new Action(u.OnReceiveSuccess));
                }
                else
                {
                    Scheduler.PostTask(new Action(u.Close));
                }
            }
            catch (ObjectDisposedException ex)
            {
                Debug.LogWarning("Socket receive 0 : " + ex.Message);
            }
            catch (Exception ex)
            {
                logger.LogWarning("Socket receive 1 : " + ex.Message);
                Scheduler.PostTask(new Action(this.Close));
            }
        }

        public void OnReceiveSuccessData(int receivelength)
        {
            if (!this.mNetReceiveBody.IsHaveEmpty(receivelength))
            {
                logger.LogError("NetClient receive buffer submit out of range.");
            }

            //断包，粘包处理
            while (this.mNetReceiveBody.GetBodyLength() >= 4)
            {
                //返回由字节数组中指定位置的四个字节转换来的 32 位无符号整数，前4个字节包含当前包体长度
                uint bodylength = BitConverter.ToUInt32(this.mNetReceiveBody.GetBody(), this.mNetReceiveBody.GetOffset());
                //断包了，则返回继续接收
                if (this.mNetReceiveBody.GetBodyLength() < (4 + bodylength)) //4byte头+包体长度
                {
                    break;
                }

                //临时包体数组空间不足时
                if (this.tmpReceiveData.Length < bodylength)
                {
                    this.tmpReceiveData = new byte[bodylength];
                }
                //从包体中取出数据，跳过4byte头
                Array.Copy(this.mNetReceiveBody.GetBody(),this.mNetReceiveBody.GetOffset() + 4, this.tmpReceiveData, 0L, bodylength);

                this.mNetReceiveBody.SetOffset((int)(bodylength + 4));
                if (this.receiveaction != null)
                {
                    try
                    {
                        this.receiveaction(this.tmpReceiveData, bodylength);
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex.ToString());
                    }
                }
            }
        }

        #endregion

      
    }
}

